function TV ( name, a )
{
	if ( ! a ) a = {};
	this._attributes = a;
	// INIT VARIABLES AND PARAMS
	this.dest_div = a.get ( 'dest_div', name );
	name = name || "TV-" + dest_div;
	this._name = name;
	this._config ();
	this.css = {};

	//dom elements ids
	this.cnt_id = 'os3tbv-cnt-' + name;		

	this.thumbs = []; // thumbs array
	this.data = [];	 // relative thumbs data

	this.last_pos = 0;
	this.start_pos = 0;
	this._transform = liwe.utils._dom_supports ( 'transform' );

	this._id_pattern = 'os3tbv-%s-%s-%s'; // type ( thumb, cnt, etc. ), inst name, index
	
	//CBACKS
	this.cbacks = {
			'click' : a.get ( 'click', null ),		//items on click generic function
			'pre_render' : a.get ( 'pre_render', null ),
			'post_render' : a.get ( 'post_render', null ),
			'after_replace' : a.get ( 'after_replace', null )
			};

	this._initialized = false;
	TV._instances [ name ] = this;
	return this;
};

TV.prototype.init = function ()
{
	var a = this._attributes;
	//force = true while called from init
	this._get_css_styles ( null, true );

	var thumb_w = ( a.get ( 'thumb_width' ) ) ? a.thumb_width : this.css [ '.os3tbv-thumb' ][ 'width' ]; 
	var thumb_h = ( a.get ( 'thumb_height' ) ) ? a.thumb_height : this.css [ '.os3tbv-thumb' ][ 'height' ];

	this.arrow_w = ( a.get ( 'arrow_width' ) ) ? a.arrow_width : this.css [ '.os3tbv-arrow-left' ][ 'width' ]; 
	this.arrow_h = ( a.get ( 'arrow_height' ) ) ? a.arrow_height : this.css [ '.os3tbv-arrow-left' ][ 'height' ];

	this.templates = thumbviewer_templates.clone ();
	if ( a.get ( 'templates' ) ) this.templates.inherits ( a [ 'templates' ], true );

	this.transition = liwe.utils.supports ( "transition" );

	
	// DEFINE VALUES
	var thumb_css = this.css [ '.os3tbv-thumb' ];
	var thumb_sizes = this._thumb_sizes ();
	this.thumb_spacing= ( a.get ( 'thumb_spacing' ) ) ? a.thumb_spacing : thumb_sizes [ 1 ]; 
	this.thumb_spacing_v= ( a.get ( 'thumb_spacing_v' ) ) ? a.thumb_spacing_v : thumb_sizes [ 0 ]; 
	this.thumb_w_full = Math.floor ( thumb_w + ( this.thumb_spacing * 2 ) );
	this.thumb_h_full = Math.floor ( thumb_h + ( this.thumb_spacing_v * 2 ) );
	this.items_to_show = ( a.get ( 'items_to_show' ) ) ? a.items_to_show : 7;

	this.pos_selected = Math.ceil ( this.items_to_show / 2 );

	this.cnt_width = a.get ( 'cnt_width', 0 );
	this.cnt_type = ( this.cnt_width > 0 ) ? 'fixed' : 'auto'; 
	//var cnt_width = thumb_w_full * this.items_to_show; 
	
	this._container_size ( this.cnt_type );
	this._initialized = true;
};

TV._instances = {};

TV.prototype.clear = function ()
{
	this._config ();
	this.css = {};

	this.thumbs = []; // thumbs array
	this.data = [];	 // relative thumbs data

	this.last_pos = 0;
	this.start_pos = 0;

	//CBACKS
	this.cbacks = {
			'click' : null, 
			'pre_render' :  null,
			'post_render' : null,
			'after_replace' : null 
			};
	this.init ();
};

TV.prototype._config = function ()
{
	this._class_names = { '.os3tbv-container':'', '.os3tbv-thumb':'', '.os3tbv-arrow-left':'', '.os3tbv-arrow-right':'' };
	this._css_files = { 'thumbviewer': '', 'style': '', 'position': '' };
	this._class_prefix = { 'os3tbv': '' };
};

TV.prototype.get_config = function ()
{
	return { '_class_names' : this._class_names.keys (), '_css_files' : this._css_files.keys (), '_class_prefix' : this._class_prefix.keys () };
};

/* add custom configuration */
/*
NOTE: 
- old values will be overwritten. 
- You have to set the right order in wich css files will be parsed.	
- the list of values must be passed as an array. 
*/
TV.prototype.set_config = function ( css_files, classes, class_prefix )
{
	//assign new values to config
	if ( classes ) this._class_names = classes;
	var self = this;
	var assign = { '_class_names' : classes, '_css_files' : css_files, '_class_prefix' : class_prefix };
	assign.iterate ( function ( v, k )
		{
			if ( ! v || typeof ( v ) != 'object' ) return; 
			self [ k ] = {};
			v.iterate ( function ( key ) { self [ k ] [ key ] = '' } );
		} );
	//init again
	this.init ();
};

TV.prototype._thumb_sizes = function ()
{
	var thumb_css = this.css [ '.os3tbv-thumb' ];
	var l = thumb_css.get ( 'marginLeft', 0 ) + thumb_css.get ( 'borderLeftWidth', 0 );
	var r = thumb_css.get ( 'marginRight', 0 ) + thumb_css.get ( 'borderRightWidth', 0 );
	var t = thumb_css.get ( 'marginTop', 0 ) + thumb_css.get ( 'borderTopWidth', 0 );
	var b = thumb_css.get ( 'marginBottom', 0 ) + thumb_css.get ( 'borderBottomWidth', 0 );
	
	return [ t, r, b, l ];
};

TV.prototype._container_size = function ( cnt_type )
{
	var cnt_width, cnt_height;
	cnt_type = cnt_type || this.cnt_type;
	if ( cnt_type == 'fixed' )
	{
		cnt_height = this.cnt_height = this.thumb_h_full; 
		cnt_width = this.cnt_width;
	} else {
		var cnt_width = this.css [ '.os3tbv-container' ][ 'width' ];
		var cnt_height = this.cnt_height = this.css [ '.os3tbv-container' ][ 'height' ];
		var items = ( cnt_width - ( this.arrow_w * 2 ) ) / this.thumb_w_full ;
		this.items_to_show = Math.round ( items );
		//resize container width to meet icons size
		//this.cnt_width = ( this.items_to_show * this.thumb_w_full  ) + ( this.arrow_w * 2 ); 
		this.cnt_width = ( ( this.items_to_show * this.thumb_w_full ) + ( this.arrow_w * 2 )  );
		
	}
};

TV.prototype._set_size = function ( w, h )
{
	if ( ! $( this.cnt_id ) ) return;

	w = w || this.cnt_width;
	h = h || this.cnt_height;
	
	var sw = this.thumb_w_full * ( this.items_to_show + 2 );
	var sh = this.thumb_h_full;
	//container
	var cnt = $( this.cnt_id );
	cnt.style.width = w + 'px'; 
	cnt.style.height = h + 'px'; 
	//subcontainer with 2 extra place holders
	var sub_cnt = $( this.cnt_id + '-sub' );
	sub_cnt.style.width = sw + 'px'; 
	sub_cnt.style.height = sh + 'px'; 
	//set sub cnt position
	sub_cnt.style.position = 'relative';
	var pos_delta_h = this.thumb_w_full - this.arrow_w;
	var pos_delta_v = ( this.cnt_height > this.thumb_h_full ) ? Math.round ( this.cnt_height - this.thumb_h_full ) / 2 : 0;
	sub_cnt.style.left = "-" + pos_delta_h + "px";
	sub_cnt.style.top = pos_delta_v + "px";
	//set arrow position
	var ar_l = $( this.arrow_id_left );
	var ar_r = $( this.arrow_id_right );
	if ( ! ar_l || ! ar_r ) return;
	ar_l.style.top = '0px';
	ar_l.style.left = '0px';
	ar_r.style.top = '0px';
	ar_r.style.left = ( w - this.arrow_w ) + 'px'; 
};

TV.prototype._get_css_styles = function ( styles_prefix, force )
{
	//return already saved styles if they exists and 'force' option is not set
	if ( this.css.count () > 0 && ! force ) return;
	if ( ! document.styleSheets ) return;
	var ss = document.styleSheets;
	var css = {};
	//FIXME: user definable
	//css files to be parsed
	var styles = this._css_files; 
	//class names to be saved
	var classes = this._class_names;
	var filter, file, selector;
	
	if ( styles_prefix )
	{
		if ( typeof( styles_prefix ) == 'object' && ( styles_prefix instanceof Array) )
		{
			prefix.concat ( styles_prefix );
			return;
		}
		console.warn ( "\nThumbViewer - styles_prefix must be an array\n" );
		return;
	};
	/* IE compatible version */
	var href, css_rules, k = 0;
	for ( v in ss )
	{
		if ( ! Object.prototype.hasOwnProperty.call ( ss, v ) ) continue;
		href = ss [ v ][ 'href' ] || '-/-';	
		file = href.split ( '/' ).slice ( -1 ).toString ();
		filter = file.split ( '.' ) [ 0 ].toString ();
		//cssRules -> webkit, rules -> IE8, null -> not supported
		css_rules = ss [ v ][ 'cssRules' ] || ss [ v ][ 'rules' ] || null;
		if ( filter in styles && css_rules ) 
		{
			for ( s in css_rules )
			{
				if ( ! Object.prototype.hasOwnProperty.call ( css_rules, s ) ) continue;
				selector = css_rules [ s ][ 'selectorText' ] || null;
				if ( selector && selector in classes )
				{
					if ( ! this.css.get ( selector ) ) this.css [ selector ] = {};
					this._set_css_vals ( this.css [ selector ], css_rules [ s ][ 'style' ] );
				}
			}
		}	
	};
};
/*convert css values to Number type and assign them*/
TV.prototype._set_css_vals = function ( obj, key )
{
        var filter = [
                        'width',
                        'height',
                        'top',
                        'right',
                        'left',
                        'bottom',
			'zIndex',
			//border,
			'borderTopWidth',
			'borderRightWidth',
			'borderBottomWidth',
			'borderLeftWidth',
                        //'padding',
                        'paddingLeft',
                        'paddingRight',
                        'paddingTop',
                        'paddingBottom',
                        //'margin',
                        'marginTop',
                        'marginLeft',
                        'marginRight',
                        'marginBottom'
                        ];
        var c, k, l = filter.length, reg = /px/;
        for ( c = 0; c < l; c ++ )
	{
		k = filter [ c ];
		value = key [ k ] || '';
		//values must be in pixels
		value = ( value.indexOf ( 'px' ) ) ? Number ( value.replace ( reg, '' ) ) : 0;
		if ( isNaN ( value ) ) value = 0;
		obj [ k ] = value;
	} 

};
/*previous version (deprecated)*/
TV.prototype._format_css_vals = function ( obj, key, value )
{
	if ( ! value || value == '' ) return;
	value = value.toString (); 
	var filter = { 
			'width':'', 
			'height':'', 
			'top':'', 
			'right':'', 
			'left':'', 
			'bottom':'',
			//'padding':'',
			'paddingLeft':'',
			'paddingRight':'',
			'paddingTop':'',
			'paddingBottom':'',
			//'margin':'',
			'marginTop':'',
			'marginLeft':'',
			'marginRight':'',
			'marginBottom':''
			};
	if ( key in filter && value.indexOf ( 'px' ) > 0 )
	{
		var reg = /px/;
		value = Number ( value.replace ( reg, '' ) );
		obj [ key ] = value;
		return obj;
	} 
};
/*add item to thumbviewer
	item can be a string or an array.
	As a string it will contain the HTML needed to render item's content
	As an array it will have two keys:
		html: same content as the string option
		data: an object with item's related data you may need to store
*/
TV.prototype.add_item = function ( item )
{
	obj = TV.create_item_obj ( item );
	obj [ 'item_id' ] = this.thumbs.length || 0;
	this.thumbs.push ( obj );
};

TV.prototype.render = function ( start_pos )
{
	if ( ! start_pos ) start_pos = this.start_pos;
	if ( ! this._initialized ) this.init ();
	if ( this.cbacks [ 'pre_render' ] ) this.cbacks [ 'pre_render' ] ( this );
	this._render_layout ();
	//this._prepare_thumbs ();
	this.render_thumbs ( start_pos );
	if ( this.cbacks [ 'post_render' ] ) this.cbacks [ 'post_render' ] ( this );
};

/*create needed divs*/
TV.prototype._render_layout = function ()
{
	//if ( ! $( this.dest_div ) ) console.debug ( 'TV: no dets_div here !!!' );
	if ( ! $( this.dest_div ) ) return;
	this.arrow_id_left = String.format ( this._id_pattern, 'arrow', this._name, 'l' );
	this.arrow_id_right = String.format ( this._id_pattern, 'arrow', this._name, 'r' );
	var dct = { 
			'arr_l' : String.formatDict ( this.templates [ 'thumb-arrow-left' ], { 'arw_left_id' : this.arrow_id_left } ),	
			'arr_r' : String.formatDict ( this.templates [ 'thumb-arrow-right' ], { 'arw_right_id' : this.arrow_id_right } ),	
			'cnt_id' : this.cnt_id
		}

	var cnt = String.formatDict ( this.templates [ 'thumb-cnt' ], dct );
	$( this.dest_div, cnt );
	this._set_arrow_events ();
};

TV.prototype._set_arrow_events = function ()
{
	var self = this;
	var arrow_left = $( this.arrow_id_left );
	var arrow_right = $( this.arrow_id_right );
	var z = this.css [ '.os3tbv-container' ].get ( 'zIndex' , 0 ) + 1;
	arrow_left.style.zIndex = z;
	arrow_right.style.zIndex = z;
	liwe.events.add ( arrow_left, 'click', function ( e ) { liwe.events.stop ( e ); self.move_right (); } );
	liwe.events.add ( arrow_right, 'click', function ( e ) { liwe.events.stop ( e ); self.move_left (); } );
};

/*create thumb's place holders in subcontainer*/
TV.prototype._prepare_thumbs = function ( sliced )
{
	if ( ! $( this.cnt_id + '-sub' ) ) return;
	var sub = $( this.cnt_id + '-sub' );
	//plus 2 hidden thumbs, one for each side
	var tmp_id, t, thumb_id, thumbs = this.items_to_show + 2;
	var buf = new String.buffer ();
	for ( t = 0; t < thumbs; t ++ )
	{
		tmp_id = ( sliced && sliced.get ( t ) ) ? sliced [ t ].item_id : t;
		thumb_id = String.format ( this._id_pattern, 'thumb', this._name, tmp_id );
		buf.add ( String.formatDict ( this.templates [ 'thumb-box' ], { 'thumb_id' : thumb_id } ) );   
	}
	sub.innerHTML = buf.toString ();
	this._set_size ();
	this._thumbs_ready = true;
};

/*build new array from original thumbs with the right items order*/
TV.prototype._arrange_thumbs = function ( pos, dir )
{
	var tot = this.thumbs.count ();
	var to_show = this.items_to_show;
	var to_render = to_show + 2;
	if ( tot <= to_show ) return this.thumbs;
	this.pos_selected = Math.ceil ( to_show / 2 );
	var sel_pos = this.pos_selected + 1; 
	var curr_pos = ( ( this.last_pos + pos ) - sel_pos ) % tot;
	var left = ( tot + curr_pos ) % tot;
	var i, tmp, res = [];//this.thumbs.slice ( left, to_render + 1 );
	for ( i = 0; i < to_render; i++ )
	{
		tmp = ( left + i ) % tot;
		res.push ( this.thumbs [ tmp ] );
		//console.debug ( "thumviewer: _arrange_thumbs - pos = %s , tmp = %s , item =  %o\n", pos, tmp, this.thumbs [ tmp ] );
	};
	this.last_pos = this.last_pos + pos;
	return res;
};

/*content and attributes for single item*/
TV.prototype._single_item = function ( item )
{
	var thumb_id = String.format ( this._id_pattern, 'thumb', this._name, item.item_id );
	//console.debug ( "Thumviewer: _single_item thumb_id = %s\n" , thumb_id );
	tmb = $( thumb_id );
	if ( ! tmb ) return;
	tmb.innerHTML = item [ 'html' ];
	tmb.setAttribute ( 'rel', item.item_id );
	//define on click cback
	cback = item [ 'data' ].get ( 'click', this.cbacks.click );
	//if found assign it to the dom element
	if ( cback ) liwe.events.add ( tmb, 'click', function ( e ) { liwe.events.stop ( e ); cback ( item [ 'data' ] ); } );
};

/*highlight selected position*/
TV.prototype._highlight_selected_pos = function ()
{
	var sub = $( this.cnt_id + '-sub' );
	if ( ! sub ) return;
	var n, nodes = sub.childNodes, l = nodes.length;
	for ( n = 0; n < l; n ++ )
	{ 
		liwe.dom.del_class ( nodes [ n ], 'os3tbv-selected' );
	}
	var tmb = sub.childNodes [ this.pos_selected ];
	if ( tmb ) liwe.dom.add_class ( tmb, 'os3tbv-selected' );
};

/*
render thumbs content in place holders 
	start_pos is the index from which to start
*/
TV.prototype.render_thumbs = function ( start_pos, sliced )
{
	start_pos = start_pos || 0;
	//clone original thumbs object
	if ( ! sliced ) sliced = this._arrange_thumbs ( start_pos );

	var thumb_id, tmb, cback = null, t = 0;
	var self = this;
	if ( ! this._thumbs_ready ) this._prepare_thumbs ( sliced );
	sliced.iterate ( function ( item ) { self._single_item ( item ); } );
	this._highlight_selected_pos ();
	//console.debug ( "Thumbviever: last_pos = %s, pos_selected = %s ", this.last_pos, this.pos_selected );
};

/*replace hidden thumbs positions when moving items*/
TV.prototype._replace_thumbs = function ( pos )
{
	var sub = $( this.cnt_id + '-sub' );
	var first = sub.firstChild;
	var last = sub.lastChild; 
	var left = parseInt ( sub.style.left, 10 );
	var dir = ( pos > 0 ) ? 'left' : 'right';
	var el, render, insert, cloned, sliced = this._arrange_thumbs ( pos );
	var self = this;

	function _update_el ()
	{
		insert = render.item_id;//Number ( last.getAttribute ( 'rel' ) ) + 1;
		el.setAttribute ( 'id' , String.format ( self._id_pattern, 'thumb', self._name, insert ) );
		el.setAttribute ( 'rel' , insert );
		cloned = el.cloneNode ( true );
		sub.style.left = ( left + ( self.thumb_w_full  * pos ) ) + 'px';
		liwe.dom.remove_element ( el, sub );
	}

	if ( dir == 'left' )
	{
		render = sliced.pop ();
		el = first;
		_update_el ();
		sub.appendChild ( cloned );
	}
	if ( dir == 'right' )
	{
		render = sliced.shift ();
		el = last; 
		_update_el ();
		sub.insertBefore ( cloned, first );
	}
	
	this._single_item ( render );
	this._highlight_selected_pos ();
	if ( this.cbacks.after_replace ) this.cbacks.after_replace ( cloned, this );

	return sliced;
};

TV.prototype._move_it = function ( pos )
{
	if ( this.thumbs.length <= this.items_to_show ) return;
	var sub_cnt = $( this.cnt_id + '-sub' );
	var delta = ( pos > 0 ) ? this.thumb_w_full * - 1 : this.thumb_w_full;
	var self = this;
	if ( this._transform )
	{
		liwe.fx.move ( sub_cnt, delta, 0, function ()
			{
				self._replace_thumbs ( pos );
			} ); 
		return;
	};
	//browser doesn't supports css transform
	var curr = parseInt ( sub_cnt.style.left ) + delta;
	sub_cnt.style.left = curr + 'px';
	this._replace_thumbs ( pos );
};

TV.prototype.move_right = function ()
{
	var pos = - 1;
	this._move_it ( pos );
};

TV.prototype.move_left = function ()
{
	var pos = + 1;
	this._move_it ( pos );
};

TV.prototype.close = function ()
{
	this._rendered = false;
	this._thumbs_ready = false;
	var cnt = $( this.cnt_id );
	if ( ! cnt ) return this.last_pos;
	var p = cnt.parentNode;
	liwe.dom.remove_element ( cnt, p ); 
	return this.last_pos;
};

//	METHODS		//

TV.get_instance = function ( name )
{
	var inst = TV._instances.get ( name, false );
	return inst;
};
/* prepare item object */
TV.create_item_obj = function ( item )
{
	var obj = { 'data' : {}, 'html' : item };
	if ( typeof ( item ) == 'object' )
	{
		obj.html = item.get ( 'html', '' );
		obj.data = item.get ( 'data', {} );
	}
	return obj;
};
TV.test = function ( name )
{
	var a = null;// { 'click' : function ( data ) { alert ( data.value ); } };
	var xx = new TV ( name, a );
	xx.add_item ( { html:'1 prova', data: { 'value':1 } } );
	xx.add_item ( { html:'2 oggetti', data: { 'value':2 } } );
	xx.add_item ( { html:'3 casa', data: { 'value':3 } } );
	xx.add_item ( { html:'4 pig', data: { 'value':4 } } );
	xx.add_item ( { html:'5 test', data: { 'value':5 } } );
	xx.add_item ( { html:'6 os3', data: { 'value':6 } } );
	xx.add_item ( { html:'7 aaa', data: { 'value':7 } } );
	xx.add_item ( { html:'8 bbb', data: { 'value':8 } } );
	xx.add_item ( { html:'9 ccc', data: { 'value':9 } } );
	xx.render ( 4 );
	return xx;
	
};	
